#include "BookmarkManager.h"

#include "CodeEditor.h"

#include <QtCore>

static const char* const keyConfigFile = "Bookmarks";
static const char* const keyBookmarks = "Bookmarks";

namespace Bookmarks {

BookmarkManager::BookmarkManager(CodeEditor* editor)
    : QObject(editor)
    , _editor(editor)
{
    updateActionStatus();
}

BookmarkManager::~BookmarkManager()
{
    DirectoryFileBookmarksMap::iterator it, end;
    end = _bookmarksMap.end();
    for (it = _bookmarksMap.begin(); it != end; ++it) {
        FileNameBookmarksMap* bookmarks = it.value();
        qDeleteAll(bookmarks->values());
        delete bookmarks;
    }
}

void BookmarkManager::toggleBookmark()
{
    toggleBookmark(_editor->file()->fileName(), _editor->currentLine());
}

void BookmarkManager::toggleBookmark(const QString& fileName, int lineNumber)
{
    // Remove any existing bookmark on this line
    if (Bookmark* mark = findBookmark(fileName, lineNumber)) {
        removeBookmark(mark);
        return;
    }

    // Add a new bookmark if no bookmark existed on this line
    Bookmark* bookmark = new Bookmark(fileName, lineNumber, this);
    addBookmark(bookmark);
}

void BookmarkManager::updateBookmark(Bookmark* bookmark)
{
    saveBookmarks();
}

void BookmarkManager::removeAllBookmarks()
{
    DirectoryFileBookmarksMap::const_iterator it, end;
    end = _bookmarksMap.constEnd();
    for (it = _bookmarksMap.constBegin(); it != end; ++it) {
        FileNameBookmarksMap* files = it.value();
        FileNameBookmarksMap::const_iterator jt, jend;
        jend = files->constEnd();
        for (jt = files->constBegin(); jt != jend; ++jt) {
            delete jt.value();
        }
        files->clear();
        delete files;
    }
    _bookmarksMap.clear();
}

void BookmarkManager::removeBookmark(Bookmark* bookmark)
{
    const QFileInfo fi(bookmark->filePath());
    FileNameBookmarksMap* files = _bookmarksMap.value(fi.path());

    FileNameBookmarksMap::iterator i = files->begin();
    while (i != files->end()) {
        if (i.value() == bookmark) {
            files->erase(i);
            delete bookmark;
            break;
        }
        ++i;
    }
    if (files->count() <= 0) {
        _bookmarksMap.remove(fi.path());
        delete files;
    }

    updateActionStatus();
    saveBookmarks();
}

bool BookmarkManager::gotoBookmark(Bookmark* bookmark)
{
    _editor->gotoLine(bookmark->lineNumber());
    return _editor->currentLine() == bookmark->lineNumber();
}

void BookmarkManager::nextInDocument()
{
    documentPrevNext(true);
}

void BookmarkManager::prevInDocument()
{
    documentPrevNext(false);
}

void BookmarkManager::documentPrevNext(bool next)
{
    int editorLine = _editor->currentLine();
    QFileInfo fi(_editor->file()->fileName());
    if (!_bookmarksMap.contains(fi.path()))
        return;

    int firstLine = -1;
    int lastLine = -1;
    int prevLine = -1;
    int nextLine = -1;
    const QList<Bookmark*> marks = _bookmarksMap.value(fi.path())->values(fi.fileName());
    for (int i = 0; i < marks.count(); ++i) {
        int markLine = marks.at(i)->lineNumber();
        if (firstLine == -1 || firstLine > markLine)
            firstLine = markLine;
        if (lastLine < markLine)
            lastLine = markLine;
        if (markLine < editorLine && prevLine < markLine)
            prevLine = markLine;
        if (markLine > editorLine
            && (nextLine == -1 || nextLine > markLine))
            nextLine = markLine;
    }

    if (next) {
        if (nextLine == -1)
            _editor->gotoLine(firstLine);
        else
            _editor->gotoLine(nextLine);
    } else {
        if (prevLine == -1)
            _editor->gotoLine(lastLine);
        else
            _editor->gotoLine(prevLine);
    }
}

BookmarkManager::State BookmarkManager::state() const
{
    if (_bookmarksMap.empty()) {
        return NoBookmarks;
    }

    const QFileInfo fi(_editor->file()->fileName());

    const DirectoryFileBookmarksMap::const_iterator dit = _bookmarksMap.constFind(fi.path());
    if (dit != _bookmarksMap.constEnd())
        return HasBookmarksInDocument;

    return HasBookmarks;
}

void BookmarkManager::updateActionStatus()
{
    emit updateActions(state());
}

/**
 * Returns the bookmark at the given file and line number.
 * Returns 0 if no such bookmark exists.
 */
Bookmark* BookmarkManager::findBookmark(const QString& fileName, int lineNumber)
{
    QFileInfo fi(fileName);
    const QString& path = fi.path();
    const QString& name = fi.fileName();
    if (_bookmarksMap.contains(path)) {
        foreach (Bookmark* bookmark, _bookmarksMap.value(path)->values(name)) {
            if (bookmark->lineNumber() == lineNumber)
                return bookmark;
        }
    }
    return 0;
}

/**
 * @param userSet  Save bookmarks now if true, otherwise we're in the progress
 *                 of loading - dont save.
 */
void BookmarkManager::addBookmark(Bookmark* bookmark, bool userSet)
{
    const QFileInfo fi(bookmark->filePath());
    const QString& path = fi.path();

    if (!_bookmarksMap.contains(path))
        _bookmarksMap.insert(path, new FileNameBookmarksMap());
    _bookmarksMap.value(path)->insert(fi.fileName(), bookmark);

    if (userSet) {
        updateActionStatus();
        saveBookmarks();
    }
}

/**
 * Adds a bookmark based on information parsed from the string.
 */
void BookmarkManager::addBookmark(const QString& s)
{
    int index2 = s.lastIndexOf(':');
    int index1 = s.indexOf(':');
    if (index2 != -1 || index1 != -1) {
        const QString& filePath = s.mid(index1 + 1, index2 - index1 - 1);
        const int lineNumber = s.mid(index2 + 1).toInt();

        if (!filePath.isEmpty() && !findBookmark(filePath, lineNumber)) {
            Bookmark* bookmark = new Bookmark(filePath, lineNumber, this);
            addBookmark(bookmark, false);
        }
    }
}

/**
 * Puts the bookmark in a string for storing it in the settings.
 */
QString BookmarkManager::bookmarkToString(const Bookmark* bookmark)
{
    const QLatin1Char colon(':');
    // Empty string was the name of the bookmark, which now is always ""
    return QLatin1String("") + colon + bookmark->filePath() + colon + QString::number(bookmark->lineNumber());
}

/**
 * Saves the bookmarks to the session settings.
 */
void BookmarkManager::saveBookmarks()
{
    QStringList list;
    foreach (const FileNameBookmarksMap* bookmarksMap, _bookmarksMap)
        foreach (const Bookmark* bookmark, *bookmarksMap)
            list << bookmarkToString(bookmark);

    QSettings s(QSettings::defaultFormat(), QSettings::UserScope,
                qApp->organizationName(), keyConfigFile);
    s.setValue(keyBookmarks, list);
}

/**
 * Loads the bookmarks form the session settings.
 */
void BookmarkManager::loadBookmarks()
{
    removeAllBookmarks();
    QSettings s(QSettings::defaultFormat(), QSettings::UserScope,
                qApp->organizationName(), keyConfigFile);

    const QStringList& list = s.value(keyBookmarks).toStringList();
    foreach (const QString& bookmarkString, list)
        addBookmark(bookmarkString);

    updateActionStatus();
}

} // namespace Bookmarks
